<?php

namespace EIP;

require_once( dirname(__FILE__).'/EIP_Authenication_Interface.php' );

Class EIP_Authenication implements Authenication_Inteface {
	protected $timeout_mins = 10;
	protected $max_attempts = 3;
	protected $table_prefix = null;
	protected $error_code = 0;
	protected $hash_iterations = 20;
	
	public function getMaxAttempts()
	{
		return $this->max_attempts;
	}
	public function __construct($prefix='eip_') {
		$this->logger = \E::get_logger(get_class($this));
		
		$this->table_prefix = $prefix;
		
		$this->timeout_mins = \E::get_setting("{$this->table_prefix}authenication.attempts_timeout",10);
		$this->max_attempts = \E::get_setting("{$this->table_prefix}authenication.max_attempts",3);
		
		
	}
	
	
	private function getSalt() {
		//check config for manually set one first, 
		//then check resources/private/keys/salt for one
		//if one does note exist, create one using time, server_ip, server_hostname,
		if (\E::get_setting("{$this->table_prefix}authenication.salt"))
			return \E::get_setting("{$this->table_prefix}authenication.salt");
		
		if ( file_exists("{BASE_DIRECTORY}/resouces/private/keys/{$this->table_prefix}authenication.salt") )
			return file_get_contents( "{BASE_DIRECTORY}/resouces/private/keys/{$this->table_prefix}authenication.salt" );
		
		$this->logger->INFO("generating site salt.");
		
		//if we are here, time to make a new salt
		seed(microtime());
		$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
		$size = strlen( $chars );
		for( $i = 0; $i < 64; $i++ ) {
			$str .= $chars[ rand( 0, $size - 1 ) ];
		}
		seed();
		$this->logger->INFO("Storing Generated Salt at '{BASE_DIRECTORY}/resouces/private/keys/{$this->table_prefix}authenication.salt' ");
		
		file_put_contents("{BASE_DIRECTORY}/resouces/private/keys/{$this->table_prefix}authenication.salt");
		return $salt;
		
	}
	public function isLoggedIn() {
		//is user logged in?
		return isset($_SESSION["{$this->table_prefix}login"]);
	}
	public function hasAccess($controller,$action='',$type='read') {
		$type = strtolower($type);
		$controller = strtolower($controller);
		$action = strtolower($action);
		
		if(!$this->isLoggedIn())
		{
			$this->logger->error("User is not logged in and is attempting to access: {$controller} -> {$action} ");
			$this->error_code = Authenication_ErrorCodes::Not_Logged_In;
			return false;
		}
		
		$this->logger->debug("Checking for cached ACL access to '{$controller}' -> '{$action}' for type of '{$type}'.");
		//check for cached acl
		if(isset($_SESSION["{$this->table_prefix}cached_acls"]) &&
			isset($_SESSION["{$this->table_prefix}cached_acls"][$controller]) &&
			isset($_SESSION["{$this->table_prefix}cached_acls"][$controller][$action]) &&
			array_search($type,$_SESSION["{$this->table_prefix}cached_acls"][$controller][$action]) !==false )
			return true; 
		
		
		$group_str = '';
		$vals = array(':controller' => $controller, ':action' => $action, ':type' => $type);
		if(isset($_SESSION["{$this->table_prefix}groups"]) && count($_SESSION["{$this->table_prefix}groups"])>0)
		{
			//look for cached group strings
			
			if(!isset($_SESSION["{$this->table_prefix}gary"])) {
				$groups = $_SESSION["{$this->table_prefix}groups"];
				
				$gary = array();
				foreach($groups as $key => $group)
				{
					$gary[":group_{$key}"] = $group;
				}
				
				$group_str = 'group IN ('.implode(array_keys($gary)).')';
				$_SESSION["{$this->table_prefix}gary"] = $gary;	
				$vals = array_merge($vals,$gary);
				
			} else {
				$group_str = 'group IN ('.implode(array_keys($_SESSION["{$this->table_prefix}gary"])).')';
				$gary = $_SESSION["{$this->table_prefix}gary"];
				$vals = array_merge($vals,$gary);
				
			}
			$this->logger->trace("Group Access Str: {$group_str}");
		}
		
		
		//check to see if the acl even exists
		
		
		$this->logger->trace("Sql: SELECT COUNT(*) FROM {$this->table_prefix}acl_routes WHERE controller = :controller AND action = :action AND type = :type AND ( email = :email OR group IN({$group_str}) ");
		
		
		$access = \R::getCol(" SELECT COUNT(*) FROM {$this->table_prefix}acl_routes WHERE controller = :controller AND action = :action AND type = :type AND ( email = :email OR group IN({$group_str}) ", $vals );
		if($access && $access > 0)
		{
			$this->logger->debug("Access granted}");
			//setup a cached acl entry for this
			$entry = array(
							$action => array($type)
						);
			
			
			if(!isset($_SESSION["{$this->table_prefix}cached_acls"]))
				$_SESSION["{$this->table_prefix}cached_acls"] = array($controller => $entry);
			else if(!isset($_SESSION["{$this->table_prefix}cached_acls"][$controller]))
				$_SESSION["{$this->table_prefix}cached_acls"][$controller] = $entry;
			else if(!isset($_SESSION["{$this->table_prefix}cached_acls"][$controller][$action]))
				$_SESSION["{$this->table_prefix}cached_acls"][$controller][$action] = array($type);
			else if(array_search($type,$_SESSION["{$this->table_prefix}cached_acls"][$controller][$action])===false)
				$_SESSION["{$this->table_prefix}cached_acls"][$controller][$action][] = $type;
			
			return true;
			
		}
		$email = $_SESSION["{$this->table_prefix}email"];
		$this->logger->error("Access Denied to user '{$email}' at '{$controller}' -> '{$index}'.");
		$this->error_code = Authenication_ErrorCodes::Access_Denied;
		return false;
	}
	public function getLoginAttempts($email=null) {
		$email = strtolower($email);
		return ($email) ? 
		\R::getCell( " SELECT COUNT(*) FROM {$this->table_prefix}auth_attempts_ip WHERE ip_address = :ip_address AND success = 0 AND when > :when ", array(':ip_address' => $_SERVER['REMOTE_ADDR'], ':when' => strtotime("-{$this->timeout_mins} mins")) )
			: \R::getCell( " SELECT COUNT(*) FROM {$this->table_prefix}auth_attempts_email WHERE email = :email AND success = 0 AND when > :when ", array(':email' => $email, ':when' => strtotime("-{$this->timeout_mins} mins")) );
		//default to check ip attempts
		
	}
	public function login($email,$password) {
		$email = strlower($email);
		
		//use crypt function for blowfish hashing.. this should be more secure than using hash functions since it is slower
		$password = crypt ( $password, '$2a$07$'.$username.$this->getSalt() );
		
		
		$this->logger->debug("Attempting login as {$email};");
		
		//check how many login attempts from this ip in last x mins
		$count = \R::getCell( " SELECT COUNT(*) FROM {$this->table_prefix}auth_attempts_ip WHERE ip_address = :ip_address AND success = 0 AND when > :when ", array(':ip_address' => $_SERVER['REMOTE_ADDR'], ':when' => strtotime("-{$this->timeout_mins} mins")) );
		
		if($count && $count >=$this->max_attempts)
		{
			$this->logger->error("Too many login attempts($this->max_attempts) by IP: '{$_SERVER['REMOTE_ADDR']}' in last '{$this->timeout_mins}' mins");
			$this->error_code = Authenication_ErrorCodes::Too_Many_Attempts;
			return false;
		}
		
		//check for attempts to the user in last x mins as well
		$count = \R::getCell( " SELECT COUNT(*) FROM {$this->table_prefix}auth_attempts_email WHERE email = :email AND success = 0 AND when > :when ", array(':email' => $email, ':when' => strtotime("-{$this->timeout_mins} mins")) );
		
		if($count && $count >= $this->max_attempts)
		{
			$this->logger->error("Too many login attempts($this->max_attempts) by IP: '{$_SERVER['REMOTE_ADDR']}' in last '{$this->timeout_mins}' mins");
			$this->error_code = Authenication_ErrorCodes::Too_Many_Attempts;
			return false;
		}
		
		
		$this->logger->trace("Logging login attempt");
		//log login attempt
		$ip_attempt = \R::load("{$this->table_prefix}auth_attempts_ip");
		//make sure we have unique index on ip_address, when
		$ip_attempt->setMeta("buildcommand.unique.0",array( "ip_address", "when") );
		$ip_attempt->ip_address = $_SERVER['REMOTE_ADDR'];
		$ip_attempt->when = time();
		
		
		$user_attempt = \R::load("{$this->table_prefix}auth_attempts_email");
		//make sure we have unique index on email, when
		$user_attempt->setMeta("buildcommand.unique.0",array( "email", "when") );
		$user_attempt->email = $email;
		$user_attempt->when = time();
		
		
		$data = \R::findOne("{$this->table_prefix}auth_users", ' email = :email AND password = :password ', array(':email' => $email, ':action' => $password) );
		
		//did we find the user?
		if($data)
		{
			//ensure email is unique column
			$data->setMeta("buildcommand.unique.0",array( "email") );
			
			//success we found the user 
			$this->logger->info("'{$username}' logged in from '{$_SERVER['REMOTE_ADDR']}'");
			
			//log login
			$data->last_login = time();
			$data->last_ip = $_SERVER['REMOTE_ADDR'];
			$data->store();
			
			$ip_attempt->success=1;
			$user_attempt->success=1;
			
			
			$_SESSION["{$this->table_prefix}login"] = mktime();
			
			//set up some session data
			$_SESSION["{$this->table_prefix}email"] =  $email;
			
			//get user groups
			$_SESSION["{$this->table_prefix}groups"] = \R::getAll( " select * from {$this->table_prefix}groups WHERE email = :email ",array(':email' => $email) );
		} else { //coult not find user.. Could be username, could be password, could be nonexistant user. We don't really care... 
			$ip_attempt->success=0;
			$user_attempt->success=0;
			
			$this->logger->error("'{$username}' failed login attempt from '{$_SERVER['REMOTE_ADDR']}'");
			$this->error_code = Authenication_ErrorCodes::No_Match_Found;
		}
		
		$ip_attempt->store();
		$user_attempt->store();
		
		return $ip_attempt->success;
	}

	public function logout()
	{
		
	}
	public function getErrorCode()
	{
		return $this->error_code;
	}
	public function getErrorMessage()
	{
		$errmsg = '';
		switch($this->error_code) {
			case 0; //no error
				break;
			case Authenication_ErrorCodes::Too_Many_Attempts:
				$errmsg = 'You have attempted to login to many times.';
				break;
			default://we don't want to give any feedback to user about exactly went wrong. This could allow users to identify valid users in db, etc... so lets just give a generic login error message for anything but too many attempts error
				$errmsg = 'Login failed. Please check your email and password are correct and try again or <a href="'.\E::get_url('EIP','forgot_password').'">reset your password</a>';
		}
		return $errmsg;
	}
	
}

?>
